#include "input.h"

/**
 * 01.03.12 - Steven Hartin - Copied over from a previous project, added
 *                            comments and slighlty modified code to work with
 *                            this project.
 */

CInputDevice::CInputDevice(const INPUT_DEVICE nDeviceType) :   
    m_nDeviceType(nDeviceType),
    m_pDevice(nullptr),
    m_bActive(false)
{
    /**
     * Constructor, this needs to store the device type of the device any
     * children represent.
     *
     * @param const INPUT_DEVICE nDeviceType The type of device the child
     *                                       represents.
     */
}

CInputDevice::~CInputDevice() 
{
    /**
     * Destructor, ensure that all direct input devices are cleared up
     * on destruction to free memory.
     */
    cleanUp();
}

bool CInputDevice::isActive() const
{
    /**
     * @return bool Whether the device is active and available.
     */
    return m_bActive;
}

void CInputDevice::setActive(bool bActive)
{
    /**
     * @param bool bActive Whether or not the input device is active.
     */
    m_bActive = bActive;
}

INPUT_DEVICE CInputDevice::getType() const
{
    /**
     * @return INPUT_DEVICE The current type of the device.
     */
    return m_nDeviceType;
}

void CInputDevice::cleanUp()
{
    /**
     * Cleans up the data and frees the memory if possible.
     */
    if (m_pDevice != nullptr)
    {
        // The device is acquired and loaded, free memory.
        m_pDevice->Unacquire();
        m_pDevice->Release();
        m_pDevice = nullptr;
    }
}

LPDIRECTINPUTDEVICE8 *CInputDevice::getDevice() 
{
    /**
     * @return LPDIRECTINPUTDEVICE8* The reference of the current direct input
     *                               device.
     */
    return (&m_pDevice);
}

void CInputDevice::setDevice(LPDIRECTINPUTDEVICE8 pDevice)
{
    /**
     * @param LPDIRECTINPUTDEVICE8 pDevice The direct input device this classs
     *                                     represents.
     */
    m_pDevice = pDevice;
}

void CInputDevice::registerEventListener(DEVICE_EVENT_CALLBACK pFuncCallback)
{
    /**
     * Registers a new event listener. The function must be a void function
     * which accepts a const DEVICE_EVENT& parameter. This function will be
     * called every time a new state is requested from the device.
     *
     * @param DEVICE_EVENT_CALLBACK pFuncCallback The address of a function
     *                                            to be used as the device
     *                                            callback.
     */
    m_arrCallbacks.push_back(pFuncCallback);
}

void CInputDevice::processCallbacks(const DEVICE_EVENT &deviceEvent) const
{
    /** 
     * Call all registered events for this device, passing the event details.
     *
     * @param const DEVICE_EVENT &deviceEvent The event variables.
     */
    for (unsigned int nCallback(0); nCallback < m_arrCallbacks.size(); 
         ++nCallback)
    {
        DEVICE_EVENT_CALLBACK pCallback(m_arrCallbacks.at(nCallback));
        (*pCallback)(deviceEvent);
    }    
}

CInputDeviceManager::CInputDeviceManager() : 
    m_pDirectInput(nullptr)
{
    /**
     * Constructor, ensure that the direct input device is initialised to a
     * nullptr to avoid dangling / free pointers.
     */
}

CInputDeviceManager::~CInputDeviceManager()
{
    /**
     * Destructor, clear up all direct input devices and the direct input 
     * pointer.
     */
    for (map<string, CInputDevice*>::iterator pDevice(m_arrDevices.begin());
         pDevice != m_arrDevices.end(); ++pDevice)
    {
        // Clean up all device
        delete pDevice->second;
        pDevice->second = nullptr;
    }
    if (m_pDirectInput != nullptr)
    {
        // Release the direct input pointer
        m_pDirectInput->Release();
        m_pDirectInput = nullptr;
    }
}

BOOL CInputDeviceManager::setUp(HINSTANCE hInst, HWND hwndMain)
{
    /**
     * Sets up direct input for a specific window.
     *
     * @param HINSTANCE hInst An instance to this application.
     * @param HWND hwndMain The handle to the main window for this application.
     * @return BOOL Whether or not the device was successfully set up.
     */
    if (m_pDirectInput == nullptr)
    {
        // Only ever need to set up once.
        m_hInst = hInst;
        m_hwndMain = hwndMain;

        HRESULT hResult;
        hResult = DirectInput8Create(hInst, 
                                     DIRECTINPUT_VERSION, 
                                     IID_IDirectInput8,
                                     reinterpret_cast<void**>(&m_pDirectInput), 
                                     NULL);
        if (FAILED(hResult))
        {
            return FALSE;
        }
    }
    return TRUE;
}

CInputDeviceManager *CInputDeviceManager::getInstance()
{
    /**
     * @static
     * @return CInputDeviceManager* A pointer to the singleton instance.
     */
    static CInputDeviceManager s_instance;
    return &s_instance;
}

CInputDevice *CInputDeviceManager::addDevice(string szIdentifier, 
                                              CInputDevice *pInputDevice)
{
    /**
     * Stores a reference of a dynamically created input device to the 
     * container in this object. NOTE: The input device must be created on
     * the heap, not the stack, otherwise it will crash your program during
     * destruction.
     *
     * @param string szIdentifier The identifier of the device.
     * @param CInputerDevice *pInputDevice The device to add to the device
     *                                     manager
     * @return CInputDevice* A pointer to the device just created.
     */
    pInputDevice->setUp(m_pDirectInput, m_hwndMain);
    m_arrDevices.insert(std::pair<string, CInputDevice*>(szIdentifier, 
                                                         pInputDevice));
    return pInputDevice;
}

void CInputDeviceManager::processStates()
{
    /**
     * Processes all the states of all the devices. This should be called
     * each instance we want to obtain the new 
     */
    for (map<string, CInputDevice*>::iterator pDevice(m_arrDevices.begin());
         pDevice != m_arrDevices.end(); ++pDevice)
    {
        pDevice->second->processState();
    }
}

CInputDevice* CInputDeviceManager::getDevice(string szKey) const
{
    /**
     * @param string szKey The string key of the input device
     * @return CInputDevice* The device referenced by the key
     */
    map<string, CInputDevice*>::const_iterator pDevice(
        m_arrDevices.find(szKey));
    if (pDevice != m_arrDevices.end())
    {
        return pDevice->second;
    }
    return nullptr;
}